package com.shinow.capacitor.plusins.printer.zebra;

import android.graphics.Bitmap;

import java.util.Arrays;

public class ImageTransferredZpl {
    private static final Byte LOW_LEVEL = 48;
    private static final Byte HIGH_LEVEL = 49;
    private static final Integer MIN_COLOR_DEPTH = 0;

    private static final char[] ABBREVIATION_CONTRAST = {'z', 'y', 'x', 'w', 'v', 'u', 't', 's', 'r', 'q', 'p', 'o',
            'n', 'm', 'l', 'k', 'j', 'i', 'h', 'g', 'Y', 'X', 'W', 'V', 'U', 'T', 'S', 'R', 'Q', 'P', 'O', 'N', 'M',
            'L', 'K', 'J', 'I', 'H', 'G'};
    private static final int[] ABBREVIATION_CONTRAST_COUNT = {400, 380, 360, 340, 320, 300, 280, 260, 240, 220, 200,
            180, 160, 140, 120, 100, 80, 60, 40, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2,
            1};

    private static class IntermediateData {
        public int height;
        public int width;
        public int complementWidth;
        public byte[][] dataBits;

        IntermediateData(Bitmap bitmap, int minColorDepth) {
            this.height = bitmap.getHeight();
            this.width = bitmap.getWidth();
            this.complementWidth = this.width % 8 == 0 ? this.width : this.width + 8 - this.width % 8;
            this.dataBits = createDataBits(this.height, this.complementWidth);
            convertPixel(this.dataBits, bitmap, minColorDepth);
        }

        public String getZpl() {
            return preSuffix(dataContent(this), this.complementWidth, this.height);
        }
    }

    private static byte[][] createDataBits(int height, int complementWidth) {
        byte[][] dataBits = new byte[height][complementWidth];

        for (int h = 0, hLength = dataBits.length; h < hLength; h++) {
            for (int w = 0, wLength = dataBits[h].length; w < wLength; w++) {
                dataBits[h][w] = LOW_LEVEL;
            }
        }

        return dataBits;
    }

    private static void convertPixel(byte[][] dataBits, Bitmap bitmap, int minColorDepth) {
        for (int h = 0, hLength = bitmap.getHeight(); h < hLength; h++) {
            for (int w = 0, wLength = bitmap.getWidth(); w < wLength; w++) {
                int argb = bitmap.getPixel(w, h);

                int alpha = (argb >> 24) & 0xff;
                int red = (argb >> 16) & 0xff;
                int green = (argb >> 8) & 0xff;
                int blue = (argb >> 0) & 0xff;
                int grayscale = ((255 - red) + (255 - green) + (255 - blue)) / 3 * alpha;

                dataBits[h][w] = grayscale > minColorDepth ? HIGH_LEVEL : LOW_LEVEL;
            }
        }
    }

    private static String bitTransformationByte(byte[] dataBits, int index) {
        return Integer
                .toHexString(Integer.valueOf(new String(Arrays.copyOfRange(dataBits, index * 4, index * 4 + 4)), 2))
                .toUpperCase();
    }

    private static String abbreviation(int repeat) {
        for (int i = 0, length = ABBREVIATION_CONTRAST_COUNT.length; i < length; i++) {
            if (repeat >= ABBREVIATION_CONTRAST_COUNT[i]) {
                int surplus = repeat - ABBREVIATION_CONTRAST_COUNT[i];
                return (surplus == 0 ? "" : abbreviation(repeat - ABBREVIATION_CONTRAST_COUNT[i]))
                        + ABBREVIATION_CONTRAST[i];
            }
        }

        return "";
    }

    private static int tallyEllipsis(byte[] dataBits) {
        int lineWidth = dataBits.length / 4 - 1;
        int nonEllipsisLenth = lineWidth;
        int ellipsis = 0;

        while (nonEllipsisLenth >= 0) {
            String nowByte = bitTransformationByte(dataBits, nonEllipsisLenth);

            if ((nonEllipsisLenth == lineWidth || ellipsis < 0) && "0".equals(nowByte)) {
                ellipsis--;
                nonEllipsisLenth--;
                continue;
            } else if ((nonEllipsisLenth == lineWidth || ellipsis > 0) && "F".equals(nowByte)) {
                ellipsis++;
                nonEllipsisLenth--;
                continue;
            }

            break;
        }

        return ellipsis;
    }

    private static String bitTransformationString(byte[] dataBits, int lineWidth) {
        StringBuilder stringBuilder = new StringBuilder();

        int repeat = 1;
        String lastByte = "";
        for (int w = 0; w < lineWidth; w++) {
            String nowByte = bitTransformationByte(dataBits, w);

            if (w != 0 && lastByte.equals(nowByte) && w != lineWidth - 1) {
                repeat++;
                continue;
            } else if (repeat != 1) {
                stringBuilder.deleteCharAt(stringBuilder.length() - 1);
                stringBuilder.append(abbreviation(repeat)).append(lastByte).append(nowByte);
                repeat = 1;
            } else {
                stringBuilder.append(nowByte);
            }

            lastByte = nowByte;
        }

        return stringBuilder.toString();
    }

    private static String dataContent(IntermediateData intermediateData) {
        StringBuilder stringBuilder = new StringBuilder();

        byte[] lastDataBits = {};
        for (int h = 0, hLength = intermediateData.dataBits.length; h < hLength; h++) {
            byte[] nowDataBits = intermediateData.dataBits[h];

            if (h != 0 && Arrays.equals(lastDataBits, nowDataBits)) {
                stringBuilder.append(":");
                continue;
            }

            int ellipsis = tallyEllipsis(nowDataBits);
            int lineWidth = nowDataBits.length / 4 - (ellipsis > 0 ? ellipsis : -ellipsis);
            stringBuilder.append(bitTransformationString(nowDataBits, lineWidth));

            if (ellipsis < 0) {
                stringBuilder.append(",");
            } else if (ellipsis > 0) {
                stringBuilder.append("!");
            }

            lastDataBits = nowDataBits;
        }

        return stringBuilder.toString();
    }

    private static String preSuffix(String str, int complementWidth, int height) {
        return "^GFA," +
                complementWidth * height / 8 + "," +
                complementWidth * height / 8 + "," +
                complementWidth / 8 + "," +
                str +
                "^FS";
    }

    public static String transformationImage(Bitmap bitmap) {
        return transformationImage(bitmap, MIN_COLOR_DEPTH);
    }

    public static String transformationImage(Bitmap bitmap, double transparency) {
        return transformationImage(bitmap, (int) Math.round(255 * 255 * transparency));
    }

    public static String transformationImage(Bitmap bitmap, int minColorDepth) {
        return new IntermediateData(bitmap, minColorDepth).getZpl();
    }
}
